package com.feinno.urcs.group.servlet;

import com.feinno.msrp.MsrpFuture;
import com.feinno.msrp.MsrpFutureListener;
import com.feinno.msrp.MsrpSession;
import com.feinno.msrp.MsrpSessionListenerBase;
import com.feinno.msrp.message.MsrpMessage;
import com.feinno.msrp.message.MsrpResponse;
import com.feinno.msrp.message.SendRequest;
import com.feinno.sip.message.SipResponse;
import com.feinno.urcs.content.Sdp;
import com.feinno.urcs.content.UrcsMultipartMimeContent;
import com.feinno.urcs.feature.Capability;
import com.feinno.urcs.group.GroupConfiguration;
import com.feinno.urcs.group.api.GroupMessageLogService;
import com.feinno.urcs.group.api.GroupOfflineMessageService;
import com.feinno.urcs.group.api.GroupService;
import com.feinno.urcs.group.entity.*;
import com.feinno.urcs.group.log.MessageContentType;
import com.feinno.urcs.group.log.MessageDirection;
import com.feinno.urcs.group.siptask.task.GroupMessageArgs;
import com.feinno.urcs.mobile.UserLocatorService;
import com.feinno.urcs.mobile.entity.UserType;
import com.feinno.urcs.profile.UserSessionService;
import com.feinno.urcs.profile.entity.ClientType;
import com.feinno.urcs.profile.entity.PcCapacity;
import com.feinno.urcs.profile.entity.UeAccess;
import com.feinno.urcs.sipservlet.SipSession;
import com.feinno.urcs.sipservlet.msrp.MsrpUtils;
import com.feinno.urcs.types.MobileNo;
import com.feinno.util.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sdp.SdpException;
import javax.sdp.SessionDescription;
import java.net.InetSocketAddress;
import java.net.URI;
import java.text.ParseException;
import java.util.List;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 参考 GroupChatSession
 * Created by Lei Gao on 7/4/15.
 */
public class GroupMemberDialogHasOffline extends GroupMemberDialog {
	private GroupOfflineMessageService offlineMsgService;
	private UserLocatorService userLocatorService;
	private boolean isLocal;
	private Random randomThreadId = new Random(100);
	private AtomicBoolean isSendOfflineMessage = new AtomicBoolean(false);
	private UserSessionService userSessionService;
	private GroupService groupService;
	private static final Logger LOGGER = LoggerFactory.getLogger(GroupMemberDialogHasOffline.class);

	private Object offlineLock = new Object();

	public GroupMemberDialogHasOffline(GroupServlet servlet, GroupOfflineMessageService offlineMessageService, UserLocatorService userLocatorService, UserSessionService userSessionService, GroupService groupService, GroupMessageLogService logService) {
		super(servlet, logService);
		this.offlineMsgService = offlineMessageService;
		this.userLocatorService = userLocatorService;
		this.userSessionService = userSessionService;
		this.groupService = groupService;
	}

	/**
	 * 这个地方的逻辑是:
	 * 如果MSRP 没有准备好, 则等待. 并将消息堆积到离线里面,然后继续下面逻辑.
	 * <p>
	 * MSRP 准备好后, 如果有离线,则先吧离线发完, 在发完之前所有新来的消息都往离线里面堆, 直到没有离线了.
	 * 如果离线发送过程中 某一条失败, 则将失败消息的groupMessageID 记录到离线的偏移量中.
	 *
	 * @param referBy
	 * @param contentType
	 * @param content
	 */
	public void sendText(MobileNo referBy, String contentType, long groupMessageStorageId, byte[] content) {
		try {
			// 如果MSRP 已经就绪, 则执行发消息的逻辑, 否则则把消息追加到离线后面, 不继续直接发送
			boolean hashOffline = hasOfflineMessage(getMemberMobile(), groupId, GroupOfflineMessageType.TEXT);
			// 如果有离线, 则存离线.
			if (hashOffline) {
				saveOffline(groupMessageStorageId);
				LOGGER.info(String.format("saveOfflineTextMessage hasOffline memberMobile=%s, groupId=%s, groupMessageStorageId=%s", getMemberMobile(), groupId, groupMessageStorageId));
				activeOfflineMessage();
				return;
			} else {// 如果没有离线,
				// 如果媒体通道没有准备好, 则先保存, 然后激活离线.
				if (!mediaAlready.get()) {
					saveOffline(groupMessageStorageId);
					LOGGER.info(String.format("saveOfflineTextMessage no Offline but mediaAlready=false memberMobile=%s, groupId=%s, groupMessageStorageId=%s", getMemberMobile(), groupId, groupMessageStorageId));
					activeOfflineMessage();
					return;
				}
			}
			LOGGER.info(String.format("sendmessage  no OfflineMessage and  mediaAlready=true memberMobile=%s, groupId=%s, groupMessageStorageId=%s", getMemberMobile(), groupId, groupMessageStorageId));

			// 获取对端的MSRP真实地址信息
			MsrpFuture future = msrp.sendRequest(remoteMsrpPath, contentType, content);
			future.addListener(new MsrpFutureListener() {
				@Override
				public void sendSuccess(MsrpMessage message) {
					//(String messageId, MobileNo sender, String receiverUri, String receiverSite, String groupUri, String clientType, MessageDirection messageDirection, MessageContentType contentType, String messageContent, Date messageDateTime, String clientIP, int statusCode, String reason) ;
					try {

						mediaAlready.set(true);
						try {
							logService.textMessageDownstreamLog(String.valueOf(groupMessageStorageId), referBy,
									memberMobile.toTelUri().toString(),
									"BF", getGroupUriByGrouupId(), "APP",
									MessageDirection.DOWN,
									MessageContentType.TXT, new String(content),
									DateUtil.getUTCNow(), getRemoteMsrpPath().toString(), 200, "OK");
						} catch (Exception ex) {
							LOGGER.warn("logService error :{}", ex);
						}
						LOGGER.info(String.format("sendText OK GroupId=%s Member=%s " +
								"remoteMsrpPath=%s ,content=%s", getGroupId(), getMemberMobile(), getRemoteMsrpPath(), content));
					} catch (Exception e) {
						LOGGER.error(String.format("  Group Message sendSuccess log error" +
								"To member :%s, error : {}", memberMobile), e);
					}
				}

				@Override
				public void sendFailed(MsrpMessage message, Throwable t) {
					mediaAlready.set(false);
					try {
						LOGGER.warn(String.format("sendText msrp error, User=%s Group=%s, remotePath=%s  message=%s", getMemberMobile(), getGroupId(), remoteMsrpPath, message), t);

						MsrpResponse msrpResponse = (MsrpResponse) message;
						if (!isLocal) {
							LOGGER.debug(String.format("user=%s isLocal=false  message=%s",
									getMemberMobile(), message.toString()));
						} else {
							UeAccess ueAccess = userSessionService.getUeAccess(memberMobile, Capability.ADHOC_GROUP);
							if (ueAccess != null) {
								saveOffline(groupMessageStorageId);
								GroupMessageArgs gma = new GroupMessageArgs();
								GroupInfo groupInfo = groupService.getGroupInfo(groupService.getGroupIndex(getGroupId()));
								gma.setGroupId(groupInfo.getGroupId());
								gma.setSubject(groupInfo.getSubject());
								gma.setDomain(GroupConfiguration.INSTANCE.getGroupDomain());
								gma.setConversationId(groupInfo.getConversationId());
								gma.setToMobile(memberMobile);
								servlet.activateMember(gma);
							} else {
								saveOffline(groupMessageStorageId);
							}
						}
						try {
							logService.textMessageDownstreamLog(String.valueOf(groupMessageStorageId), referBy,
									memberMobile.toTelUri().toString(),
									"BF", getGroupUriByGrouupId(), "APP", MessageDirection.DOWN,
									MessageContentType.TXT, new String(content), DateUtil.getUTCNow(),
									getRemoteMsrpPath().toString(), msrpResponse.getStatus(), msrpResponse.getDiscription());
						} catch (Exception ex) {
							LOGGER.warn("logService error :{}", ex);
						}
					} catch (Exception e) {
						LOGGER.error(String.format("send Group Message To member :%s, error : {}", memberMobile), e);
					}
				}
			});
		} catch (Exception ee) {
			LOGGER.error(String.format("sentText error ,mobileNo=%s, groupId=%s,groupMessageStorageId=%s ", memberMobile, groupId, groupMessageStorageId), ee);
		}
	}

	private void saveOffline(long groupMessageStorageId) {
		if (!isLocal) {
			LOGGER.debug(String.format("user=%s isLocal=false not saveOffline groupMessageStorageId=%s", memberMobile, groupMessageStorageId));
		} else {
			synchronized (offlineLock) {
				offlineMsgService.saveOfflineTextMessage(memberMobile, groupId, groupMessageStorageId);
			}
		}
	}

	/**
	 * 作为应答方 等待对端链接
	 *
	 * @param msrp
	 */
	public void initMsrpSession(MsrpSession msrp) {
		this.msrp = msrp;
		localMsrpPath = msrp.getSessionId();
		msrp.setListener(new MsrpSessionListenerBase() {
			@Override
			public void receivedSendRequest(MsrpSession session, SendRequest send) {
				try {

					remoteMsrpPath = send.getFromPath().getFirst();
					// 为空的是心跳包
					if (send.getContentType() != null) {

						userSessionService.removePcCapacity(memberMobile, PcCapacity.IM_CC);
						servlet.reciveText(groupId, memberMobile, send.getContentType(), send.getBody(), ClientType.APP, DateUtil.getUTCNow());
						mediaAlready.set(true);
					} else {
						LOGGER.info(String.format("acitve mobile=%s groupId=%s", memberMobile, groupId));
						/**
						 * 有意识的吧链接可用状态置为false , 为了让离线消息的线程等待一下, 交出执行时间, 使得本次msrp 的应答靠前执行.
						 */
						mediaAlready.set(false);
						hadMoreMessage();
						/**
						 * 由于上面false了. 离线已经暂停执行,等待下次cpu执行时间分配, 这里为了恢复msrp 的执行状态.使得离线线程得到执行后尽快恢复业务逻辑.
						 */
						mediaAlready.set(true);
					}
				} catch (Exception e) {
					LOGGER.error(String.format("MSRP recive error sessionId=%s \r\n send=%s", session.getSessionId(), send.toString()), e);
				}
			}

			@Override
			public void sessionClosed(MsrpSession session) {
				LOGGER.info(String.format("msrp Serssion closed sessionId=%s", session.getSessionId().toString()));
				localMsrpPath = null;
				mediaAlready.set(false);
				closeSipSession();
			}
		});
	}

	public void hadMoreMessage() {
		Thread offline = new Thread(() -> activeOfflineMessage());
		offline.setName(String.format("offline_thread_group_%s_user_%s_R_%s", getGroupId(), memberMobile, randomThreadId.nextInt()));
		offline.start();

	}

	@Override
	public void cancel() {
		try {
			this.sipSession.cancel();

		} catch (Exception e) {
			LOGGER.error(String.format("cancel error GroupId=%s UserId=%s ", getGroupId(), getMemberMobile()), e);
		}
	}

	public int getGroupId() {
		return groupId;
	}

	public void setGroupId(int groupId) {
		this.groupId = groupId;
	}

	public MobileNo getMemberMobile() {
		return memberMobile;
	}

	@Override
	public void closeSipSession() {
		if (getSipSession() != null) getSipSession().bye();
		setSipSession(null);
	}

	@Override
	public void closeMsrp() {
		mediaAlready.set(false);
		if (msrp != null) msrp.close();
		setMsrp(null);
	}

	public void setMemberMobile(MobileNo memberMobile) {
		this.memberMobile = memberMobile;
		this.isLocal = userLocatorService.lookup(memberMobile).getType().equals(UserType.INNER);
	}

	public SipSession getSipSession() {
		return sipSession;
	}

	public void setSipSession(SipSession sipSession) {
		this.sipSession = sipSession;
	}

	public void setRemoteMsrpPath(URI peerMsrpPath) {
		this.remoteMsrpPath = peerMsrpPath;
	}

	public void setLocalMsrpPathBySDP(SessionDescription sdp) throws SdpException, ParseException {
		localMsrpPath = URI.create(MsrpUtils.getMsrpPath(sdp));
	}


	private boolean hasOfflineMessage(MobileNo mobileNo, int groupId, GroupOfflineMessageType messageType) {
		synchronized (offlineLock) {
			return offlineMsgService.hasOfflineMessage(memberMobile, groupId, GroupOfflineMessageType.TEXT);
		}
	}

	private void activeOfflineMessage() {
		try {
			if (!isSendOfflineMessage.get()) {
				isSendOfflineMessage.set(true);
				// 如果媒体通道没有打通, 则等待. 没有这段会有问题.
				if (!mediaAlready.get()) {
					int waitTime = 0;
					while (waitTime <= 5) {
						if (!mediaAlready.get()) {
							LOGGER.info("activeOfflineMessage mediaAlready=false wait ============" + waitTime);
							if (waitTime == 6) {
								LOGGER.info("activeOfflineMessage mediaAlready=false return ============" + waitTime);
								return;
							}

							Thread.sleep((waitTime++) * 500);


						} else {
							break;
						}
					}
				}
				if (msrp != null && remoteMsrpPath != null) {
					// 这个地方会不会有问题, 导致一直get True .然后死循环?
					while (hasOfflineMessage(memberMobile, groupId, GroupOfflineMessageType.TEXT)) {
						List<GroupTextOfflineMessage> offlineMessages = offlineMsgService.getOfflineTextMessage(memberMobile, groupId, 100);
						if (offlineMessages == null) break;
						for (GroupTextOfflineMessage offlineMessage : offlineMessages) {

							GroupTextMessageStorageEntity storageEntity = offlineMessage.getGroupTextMessageStorage();
							// 如果是自己发的, 而且发送的客户端是XXX 类型, 则不给自己下离线了.
							if (storageEntity.getSender().equals(getMemberMobile()) && ClientType.APP.equals(storageEntity.getMessageClientType())) {
								offlineMsgService.deliveredTextMessage(memberMobile, groupId, offlineMessage);
								continue;
							}
							LOGGER.info(String.format("try sendOffline " +
											"GroupId=%s Mobile=%s offLineMessage=%s",
									getGroupId(), getMemberMobile(), offlineMessage));

							MsrpFuture future = msrp.sendRequest(remoteMsrpPath,
									offlineMessage.getContentType(), offlineMessage.getContent());

							WaitMsrpFutureListener waitListener = new WaitMsrpFutureListener();
							future.addListener(waitListener);
							// sleep 会有问题啊.
							MsrpFutureStatus status = waitListener.waitSendStatus(10);
							if (status.equals(MsrpFutureStatus.SUCCESS)) {
								offlineMsgService.deliveredTextMessage(memberMobile, groupId, offlineMessage);
								try {
									logService.textMessageDownstreamLogOffline(
											String.valueOf(offlineMessage.getGroupMessageStorageId()),
											offlineMessage.getGroupTextMessageStorage().getSender(),
											memberMobile.toTelUri().toString(),
											"BF", getGroupUriByGrouupId(),
											"APP", MessageDirection.DOWN,
											MessageContentType.TXT,
											new String(offlineMessage.getGroupTextMessageStorage().getTextContent()),
											DateUtil.getUTCNow(), getRemoteMsrpPath().toString(), 200, "OK");
								} catch (Exception ex) {
									LOGGER.warn("logService error :{}", ex);
								}
							} else {
								LOGGER.warn(String.format("return waitSendStatus error" +
												"MobileNo=%s,groupId=%s,GroupMessageId=%s, MsrpFutureStatus=%s",
										memberMobile, groupId,
										offlineMessage.getGroupMessageStorageId(), status));
								offlineMsgService.sendFail(offlineMessage.getOfflineMessageIndex());

								isSendOfflineMessage.set(false);
								try {
									logService.textMessageDownstreamLogOffline(
											String.valueOf(offlineMessage.getGroupMessageStorageId()),
											offlineMessage.getGroupTextMessageStorage().getSender(),
											memberMobile.toTelUri().toString(),
											"BF", getGroupUriByGrouupId(),
											"APP", MessageDirection.DOWN,
											MessageContentType.TXT,
											new String(offlineMessage.getGroupTextMessageStorage().getTextContent()),
											DateUtil.getUTCNow(), getRemoteMsrpPath().toString(), 500, "Fail");
								} catch (Exception ex) {
									LOGGER.warn("logService error :{}", ex);
								}
								return;
							}

						}
					}
					isSendOfflineMessage.set(false);

				} else {
					LOGGER.info("isSendOfflineMessage=true this Thread exit ");
				}
			} else {
				LOGGER.warn(String.format("msrp or remoteMsrpPath is null  MobileNo=%s,groupId=%s", memberMobile, groupId));
			}
		} catch (Exception e) {
			LOGGER.error("isSendOfflineMessage  error ", e);
			isSendOfflineMessage.set(false);
		}

	}


	protected void bindMsrp(SipResponse response) throws SdpException, ParseException {
		UrcsMultipartMimeContent multipartMimeContent = UrcsMultipartMimeContent.newInstance(response.getContentTypeHeader(), response.getRawContent());
		Sdp sdp = multipartMimeContent.getContent(Sdp.class);
		SessionDescription sd = sdp.getSdp();
		boolean isActive = MsrpUtils.isActive(sd);
		remoteMsrpPath = java.net.URI.create(MsrpUtils.getMsrpPath(sd));
		if (!isActive) {
			// 如果对端是ACTIVE，只好由我们主动链接，链接成功后返回200，失败则500
			InetSocketAddress remoteAddress = MsrpUtils.getAddressBySdp(sd);
			MsrpFuture msrpFuture = msrp.alive(remoteAddress.getHostString(), remoteAddress.getPort(), remoteMsrpPath);
			msrpFuture.addListener(new MsrpFutureListener() {
				@Override
				public void sendSuccess(MsrpMessage msrpMessage) {
					mediaAlready.set(true);
					hadMoreMessage();
					LOGGER.info(String.format("msrp active Success  mobile=%s GroupId=%s ,remoteMsrpPath=%s", getMemberMobile(), getGroupId(), remoteMsrpPath));
				}

				@Override
				public void sendFailed(MsrpMessage msrpMessage, Throwable throwable) {
					LOGGER.error(String.format("msrp active Failed  mobile=%s GroupId=%s ,remoteMsrpPath=%s", getMemberMobile(), getGroupId(), remoteMsrpPath), throwable);
				}
			});
		} else {
			LOGGER.info(String.format("msrp active Failed  mobile=%s GroupId=%s ,remoteMsrpPath=%s isActive=%s", getMemberMobile(), getGroupId(), remoteMsrpPath, isActive));
		}
	}

	class WaitMsrpFutureListener implements MsrpFutureListener {

		private MsrpFutureStatus sendStatus = MsrpFutureStatus.DEFAULT;

		@Override
		public void sendSuccess(MsrpMessage message) {
			sendStatus = MsrpFutureStatus.SUCCESS;
		}

		@Override
		public void sendFailed(MsrpMessage message, Throwable t) {
			LOGGER.error(String.format("MsrpSend sendFailed MsrpMessage=%s", message), t);
			sendStatus = MsrpFutureStatus.FAIDED;
		}


		public MsrpFutureStatus waitSendStatus(int maxWaitSecond) {
			int sumTime = maxWaitSecond * 1000;
			while (sendStatus.equals(MsrpFutureStatus.DEFAULT)) {
				try {
					Thread.sleep(10l);
					sumTime -= 10;
				} catch (InterruptedException e) {
					LOGGER.warn("waitSendStatus sleep error ", e);
				}
				if (sumTime <= 0) break;
			}
			return sendStatus;
		}
	}

	enum MsrpFutureStatus {
		DEFAULT,
		SUCCESS,
		FAIDED,
		SENDING,
		TIMEOUT,;
	}
}
